package pulpfizz.physics;

import java.util.ArrayList;

import org.jbox2d.collision.AABB;
import org.jbox2d.common.Vec2;
import org.jbox2d.dynamics.Body;
import org.jbox2d.dynamics.BodyDef;
import org.jbox2d.dynamics.World;
import org.jbox2d.dynamics.joints.GearJoint;
import org.jbox2d.dynamics.joints.Joint;
import org.jbox2d.dynamics.joints.JointType;

import pulpfizz.physics.contact.ConcreteBoundaryListener;
import pulpfizz.physics.contact.ConcreteContactListener;
import pulpfizz.physics.contact.ConcreteDestructionListener;
import pulpfizz.physics.contact.ContactData;
import pulpfizz.physics.contact.ContactEventDispatcher;
import pulpfizz.physics.contact.ContactStore;
import pulpfizz.physics.contact.PulpFizzContactFilter;

/**
 * The New World -- a better place.
 * 
 * This is simply an extension of the World object, for adding convenience methods like destroy()
 * that might otherwise be a part of the JBox2D code, except for the fact that we're lazy.
 * @author Greg
 *
 */
public class NewWorld extends World implements ContactStore
{
	public static float DEFAULT_GRAVITY_LENGTH = 10;
	int worldIterations = 10;
	float speedMultiplier = 1.0f / 1000f;
	
	AABB aabb;
	
	// Listeners
	/** Listener for body and joint destructions. */
	protected ConcreteDestructionListener m_concreteDestructionListener;
	/** Listener for world AABB violations. */
	protected ConcreteBoundaryListener m_concreteBoundaryListener;
	/** Listener for contact events direct from JBox2d. */
	protected ConcreteContactListener m_concreteContactListener;
	/** Dispatcher for contact events. */
	protected ContactEventDispatcher m_contactEventDispatcher;
	/** Filter for contact processing. */
	protected PulpFizzContactFilter m_pulpFizzContactFilter;
	
	static AABB defaultBoundaries = new AABB(new Vec2(-200f, -200f), new Vec2(
			200f, 200f));
	
	/** 
	 *  List of gear joints, which may need to be destroyed if any joints
	 *  connected to them are destroyed
	 */
	private ArrayList<GearJoint> gearJoints;

	/** Maximum contact points to store. */
	public static final int k_maxContactPoints = 2048;
	private ContactData[] m_points = new ContactData[k_maxContactPoints];
	private int m_pointCount;
	
	public NewWorld()
	{
		this(defaultBoundaries);
	}
	
	public NewWorld(AABB worldAABB) {
		this(worldAABB, new Vec2(0.0f, -10.0f));
	}

	public NewWorld(AABB worldAABB, Vec2 grav)
	{
		super(worldAABB, grav, true);
		this.aabb = worldAABB;
		setWarmStarting(true);
		setPositionCorrection(true);
		setContinuousPhysics(true);
    	m_concreteDestructionListener = new ConcreteDestructionListener(this);
		m_concreteBoundaryListener = new ConcreteBoundaryListener(this);
		m_concreteContactListener = new ConcreteContactListener(this);
		m_contactEventDispatcher = new ContactEventDispatcher(this);
		m_pulpFizzContactFilter = new PulpFizzContactFilter();
		setContactPointCount(0);
		for (int i=0; i<getContactPoints().length; ++i) {
			getContactPoints()[i] = new ContactData();
		}
		setDestructionListener(m_concreteDestructionListener);
		setBoundaryListener(m_concreteBoundaryListener);
		setContactListener(m_concreteContactListener);
		setContactFilter(m_pulpFizzContactFilter);
		setDebugDraw(null);
		gearJoints = new ArrayList<GearJoint>();
	}
	
	public ContactEventDispatcher getContactEventDispatcher() {
		return m_contactEventDispatcher;
	}

	public AABB getAABB()
	{
		return aabb;
	}

	public void update(int elapsedTime)
	{	
		step(elapsedTime * speedMultiplier, worldIterations);
	}
	
	public Body createBody(BodyDef bd) {
		Body result = super.createBody(bd);
		if (result.getUserData() == null) result.setUserData(Actor.NULL_ACTOR);
		return result;
	}
	
	/**
	 * Create a Body from a BodyDef and link it to an Actor.
	 * Note that any userdata set in the BodyDef will
	 * be overridden by the passed Actor - if you want to
	 * preserve the BodyDef's userdata, use createBody(BodyDef) instead.
	 * @param bd BodyDef to create body from.
	 * @param a Actor to link to this body.
	 * @return The created body.
	 */
	public Body createBody(BodyDef bd, Actor a) {
		Body result = super.createBody(bd);
		a.addBody(result);
		return result;
	}
	
	@Override
	public void step(float dt, int iterations)
	{	
		super.step(dt, iterations);
		//Unnecessary with post-function registry
		//m_contactEventDispatcher.emitEvents();
	}

	public void destroy()
	{
		ArrayList<Body> bodies = new ArrayList<Body>();
		Body b = getBodyList();
		while (b != null)
		{
			bodies.add(b);
			b = b.getNext();
		}
		
		for (Body bod : bodies)
		{
			destroyBody(bod);
		}
		
		
	}

	public int getWorldIterations()
	{
		return worldIterations;
	}

	public void setWorldIterations(int worldIterations)
	{
		this.worldIterations = worldIterations;
	}

	public float getSpeedMultiplier()
	{
		return speedMultiplier;
	}

	public void setSpeedMultiplier(float speedMultiplier)
	{
		this.speedMultiplier = speedMultiplier;
	}

	
	public void jointDestroyed(Joint joint) {
		// Kill off any gear joints that will get mucked up
		// by this removal.
		if ( gearJoints.size() != 0 &&
			 (joint.getType() == JointType.PRISMATIC_JOINT ||
			  joint.getType() == JointType.REVOLUTE_JOINT)) {
			for (GearJoint gj:gearJoints) {
				if (gj.m_prismatic1 == joint || gj.m_prismatic2 == joint ||
					gj.m_revolute1 == joint || gj.m_revolute2 == joint) {
					destroyJoint(gj);
				}
			}
		}
	}

	public void boundaryViolated(Body body) {
		
	}

	public void setContactPointCount(int pointCount) {
		if (pointCount > k_maxContactPoints) return;
		this.m_pointCount = pointCount;
	}

	public int getContactPointCount() {
		return m_pointCount;
	}

	public ContactData[] getContactPoints() {
		return m_points;
	}
	
	public void clearContactPoints() {
		m_pointCount = 0;
	}
	
}
